﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Threading;

using MySpring;

using SolutionOrchestrator.Validators;

namespace SolutionOrchestrator
{
	public class Analyzer : IAnalyzer
	{
		readonly DataCtx _ctx;
		readonly ISpringProvider _services;
		readonly Dispatcher _dispatcher;
		readonly IRuleSet _ruleSet;

		public Analyzer(DataCtx ctx, ISpringProvider services, Dispatcher dispatcher, IRuleSet ruleSet)
		{
			_ctx = ctx;
			_services = services;
			_dispatcher = dispatcher;
			_ruleSet = ruleSet;
			_timeout = new Timer(Worker);
		}

		readonly object _syncWorker = new object();

		Thread _th;
		FileSystemWatcher _watcher;

		// rescan only this files or ALL when collection is empty
		readonly HashSet<string> _filesToRescan = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);

		void Worker(object state)
		{
			lock (_syncWorker)
			{
				var path = _ctx.Settings.CurrentRootPath;
				var progress = _services.Get<IProgress>();

				var files = GetFilesToScan(path);

				if (ScanOnlySpecific)
				{
					// remove scheduled violations
					_dispatcher.Invoke((Action)(() =>
					{
						foreach (var file in files)
						{
							var toDel = _ctx.Violations.Where(x => file.Equals(x.FullPath, StringComparison.InvariantCultureIgnoreCase)).ToArray();
							foreach (var violation in toDel)
							{
								_ctx.Violations.Remove(violation);
							}
						}
					}));
				}
				else
				{
					// remove all
					_dispatcher.Invoke((Action)(() => _ctx.Violations.Clear()));
				}

				progress.Progress = 0;
				progress.ProgressMax = files.Length;

				var ctx = new ValidationContext { SolutionDir = path };
				foreach (var file in files)
				{
					foreach (var rule in _ruleSet.All.Where(x => x.IsEnabled))
					{
						try
						{
							foreach (var violation in rule.Validate(file, ctx))
							{
								FillViolation(violation, file);
								_dispatcher.Invoke((Action)(() => _ctx.Violations.Add(violation)));
							}
						}
						catch (Exception ex)
						{
							var violation = new Violation
							{
								Description = ex.Message,
							};
							FillViolation(violation, file);
							_dispatcher.Invoke((Action)(() => _ctx.Violations.Add(violation)));
						}
						progress.Progress++;
					}
				}
				progress.ProgressMax = 100;
				progress.Progress = 100;
			}
		}

		bool ScanOnlySpecific
		{
			get { return _filesToRescan.Any(); }
		}

		string[] GetFilesToScan(string path)
		{
			return ScanOnlySpecific ? _filesToRescan.ToArray() : Directory.GetFiles(path, "*.csproj", SearchOption.AllDirectories);
		}

		void FillViolation(Violation violation, string file)
		{
			violation.FullPath = file;
		}

		void WatcherChanged(object sender, FileSystemEventArgs e)
		{
			try
			{
				if (PathHelper.FileExists(e.FullPath))
				{
					ScheduleRescan(false, e.FullPath);
				}
			}
			catch (Exception ex)
			{
				XTrace.XTrace.Exception(ex, "Exception in FileSystemWatcher Handler");
			}
		}

		readonly Timer _timeout;

		public void ScheduleRescan(bool now = false, string file = null)
		{
			if (file != null)
			{
				// schedule rescan only modified files
				if (!Path.IsPathRooted(file))
				{
					throw new ArgumentException(@"Full path expected", "file");
				}
				_filesToRescan.Add(file);
			}
			else
			{
				// schedule rescan all
				_filesToRescan.Clear();
			}
			_timeout.Change(Timeout.Infinite, Timeout.Infinite);
			_timeout.Change(now ? 0 : 333, Timeout.Infinite);
		}

		public void RecreateWatcher()
		{
			if (_watcher != null)
			{
				_watcher.Dispose();
			}
			_watcher = new FileSystemWatcher
			{
				IncludeSubdirectories = true,
				NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName,
				Path = _ctx.Settings.CurrentRootPath,
				Filter = "*.csproj"
			};
			_watcher.Changed += WatcherChanged;
			_watcher.Deleted += WatcherChanged;
			_watcher.EnableRaisingEvents = true;
		}
	}
}
